Series.corr 另算一组股票;同时检查股票是否齐全、共同交易日是否足够,以及是否存在取值不变的列。tushare 获取A股数据的方法seaborn.heatmap() 绘制相关性热力图在现代投资组合理论中,资产间的相关性是核心概念:
皮尔逊相关系数衡量两个变量线性相关的强度和方向:
\[ \large \rho_{X,Y} = \frac{\text{Cov}(X,Y)}{\sigma_X \sigma_Y} = \frac{E[(X-\mu_X)(Y-\mu_Y)]}{\sigma_X \sigma_Y} \]
| 相关系数范围 | 含义 | 投资启示 |
|---|---|---|
| \([0.7, 1]\) | 强正相关 | 同涨同跌,分散化效果差 |
| \([0.4, 0.7)\) | 中度正相关 | 有一定联动性 |
| \([-0.1, 0.4)\) | 弱正相关/接近零 | 可进一步观察其分散风险作用 |
| \([-0.7, -0.1)\) | 中度负相关 | 可进一步研究其对冲作用 |
| \([-1, -0.7)\) | 强负相关 | 检查样本、压力期与结构变化 |
这些区间是描述性课堂语言,不构成交易或对冲结论。样本量、置信区间、滚动稳定性、交易成本与压力期相关缺一不可;同一相关系数在 20 日与 500 日样本中的不确定性不同。
投资组合方差的完整公式:
\[ \large \sigma_p^2 = \sum_i \sum_j w_i w_j \sigma_i \sigma_j \rho_{ij} \]
相关系数只能衡量线性关系,存在重要局限:
pro、stock_list、data、df 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。df 的结果;同时写出方向、数量级或表格/图形结构。使用提醒:平台固定答案中含有类似访问密钥的文字,只能在指定教学平台内按要求输入,不要复制到公开网页或其他程序中。本章后面的分析使用课程本地数据,不需要这类访问密钥。
依据诊断:本任务只用于平台固定文本录入。其缩进非法、没有按交易日期键连接、直接对价格水平求相关,不能支持任何金融相关性结论;有效解释只能绑定后文“共同交易日对齐→同频收益率→相关矩阵”的本地分析。
拓展练习:把数据替换为一个中国企业或市场序列,改变一个分组或时间窗口;说明图形结论是否改变,以及为何。
PUBLICATION_STOP。pro.daily() 与 df.corr()pro.daily():获取日线行情
ts_code:股票代码(如 '600030.SH')start_date / end_date:起止日期open/high/low/close/vol 等字段的 DataFramedf.corr():计算相关系数矩阵
sns.heatmap()sns.heatmap(df.corr(), annot=True, cmap='coolwarm') 参数详解:
| 参数 | 含义 | 本例取值 |
|---|---|---|
data |
输入的二维数据(相关系数矩阵) | df.corr() |
annot |
是否在格子中标注数值 | True |
cmap |
颜色映射方案 | 'coolwarm'(蓝红渐变) |
center |
颜色中心值 | 可设为 0 |
square |
格子是否为正方形 | 可设为 True |
| 名称 | 效果 | 适用场景 |
|---|---|---|
coolwarm |
蓝→白→红 | 通用,正负对比清晰 |
RdYlGn |
红→黄→绿 | 金融领域常用 |
RdBu |
红→白→蓝 | 色盲友好 |
viridis |
紫→绿→黄 | 感知均匀,适合打印 |
YlOrRd |
黄→橙→红 | 只有正值时 |
建议:金融热力图优先使用 'RdYlGn' 或 'coolwarm',便于直观区分正负相关。
在计算相关性前,通常先将价格转换为收益率:
简单收益率:
\[ \large R_t = \frac{P_t - P_{t-1}}{P_{t-1}} \]
对数收益率:
\[ \large r_t = \ln\left(\frac{P_t}{P_{t-1}}\right) = \ln(P_t) - \ln(P_{t-1}) \]
Python 实现:df.pct_change() 与 np.log(df / df.shift(1))
| 特性 | 简单收益率 | 对数收益率 |
|---|---|---|
| 公式 | \((P_t - P_{t-1}) / P_{t-1}\) | \(\ln(P_t / P_{t-1})\) |
| 可加性 | 多期不可直接相加 | 可加:\(r_{1 \to 3} = r_1 + r_2 + r_3\) |
| 对称性 | 同幅百分比涨跌不互相抵消 | 倒数价格因子的对数收益符号相反 |
| 分布 | 由样本决定 | 有时更接近对称,但不保证正态 |
| 应用 | 报告收益率 | 计算波动率、建模 |
相关性分析选择:同一频率、共同时间键下,简单收益率或对数收益率都可计算 Pearson 相关;应预先声明口径并做敏感性比较。时间可加性本身不是选择单期相关口径的理由。
# 计算简单收益率
# pct_change(): (当前值 - 上期值) / 上期值
simple_returns = df_prices.pct_change().dropna()
# 计算对数收益率
# ln(P_t / P_{t-1}) = ln(P_t) - ln(P_{t-1})
log_returns = np.log(df_prices / df_prices.shift(1)).dropna()
# 输出收益率预览
print('简单收益率(前5行):')
print(simple_returns.head())
print(f'\n对数收益率(前5行):')
print(log_returns.head())
# 收益率统计对比
print(f'\n简单收益率统计:')
print(simple_returns.describe().round(4))
print(f'\n对数收益率统计:')
print(log_returns.describe().round(4))
# 使用对数收益率计算相关性矩阵
# corr() 默认使用皮尔逊相关系数 (method='pearson')
corr_matrix = log_returns.corr()
# 输出相关性矩阵
print('相关性矩阵:')
print(corr_matrix.round(3))
print(f'\n矩阵形状: {corr_matrix.shape}')
# 验证对称性
print(f'\n矩阵对称性检验:')
print(f'是否对称: {np.allclose(corr_matrix, corr_matrix.T)}')
print(f'对角线元素(应全为1): {np.diag(corr_matrix)}')corr() 的三种计算方式| 参数 | 方法 | 特点 |
|---|---|---|
'pearson' |
皮尔逊(默认) | 衡量线性关系 |
'spearman' |
斯皮尔曼秩相关 | 对异常值稳健 |
'kendall' |
肯德尔 \(\tau\) 相关 | 适合小样本 |
检验原假设 \(H_0: \rho = 0\)(两变量无线性相关)
\[ \large t = \frac{r\sqrt{n-2}}{\sqrt{1-r^2}} \sim t_{n-2} \]
遇到问题时:没有合理依赖结构、重采样块长或多重检验方案时,只报告描述性相关,不发布“显著”结论。
| 符号 | FDR 调整后 q 值 | 含义 |
|---|---|---|
*** |
\(q < 0.01\) | 在当前检验族内通过 1% FDR 判断标准 |
** |
\(q < 0.05\) | 在当前检验族内通过 5% FDR 判断标准 |
* |
\(q < 0.10\) | 探索性候选,须核对 |
ns |
\(q \geq 0.10\) | 未通过当前 FDR 判断标准 |
决策边界:调整后的统计显著 \(\neq\) 经济显著。必须同时报告系数大小、置信区间、样本期与稳健性检查,不能仅凭星号作决策。
from scipy.stats import pearsonr # 在古典假设成立时计算皮尔逊检验
from statsmodels.stats.multitest import multipletests # 对矩阵内多重检验执行FDR调整
n = len(corr_matrix)
pairs = []
for i in range(n):
for j in range(i+1, n):
stock1 = corr_matrix.index[i]
stock2 = corr_matrix.columns[j]
corr = corr_matrix.iloc[i, j]
# pearsonr() 返回 (相关系数, p值)
_, p_value = pearsonr(
log_returns[stock1], log_returns[stock2]
)
pairs.append({
'股票1': stock1,
'股票2': stock2,
'相关系数': corr,
'原始P值': p_value
})
df_pairs = pd.DataFrame(pairs) # 汇总同一相关矩阵内的全部资产对
df_pairs['FDR_q值'] = multipletests(df_pairs['原始P值'], method='fdr_bh')[1] # 控制检验族的错误发现率
df_pairs['显著性'] = pd.cut(df_pairs['FDR_q值'], bins=[-1, 0.01, 0.05, 0.10, 1], labels=['***', '**', '*', 'ns']) # 依据调整后q值生成探索性标记
df_pairs = df_pairs.sort_values('相关系数', ascending=False) # 按相关强度排列输出
print(df_pairs.to_string(index=False)) # 同时展示效应量、原始p值与FDR结果
import seaborn as sns
plt.figure(figsize=(10, 8))
sns.heatmap(
corr_matrix, # 相关系数矩阵
annot=True, # 显示数值
cmap='RdYlGn', # 红-黄-绿配色
center=0, # 颜色中心值为0
square=True, # 正方形格子
linewidths=0.5, # 格子边框
fmt='.3f', # 保留3位小数
cbar_kws={'label': '皮尔逊相关系数'},
vmin=-1, vmax=1 # 固定范围[-1,1]
)
plt.title('股票收益率相关性热力图', fontsize=14, pad=20)
plt.xlabel('股票', fontsize=12)
plt.ylabel('股票', fontsize=12)
plt.tight_layout()
plt.show()sns.clustermap() 在热力图基础上添加树状图,将相似股票自动聚在一起:
from scipy.cluster.hierarchy import linkage
linkage_matrix = linkage(corr_matrix, method='average')
g = sns.clustermap(
corr_matrix, annot=True, cmap='RdYlGn',
center=0, square=True, linewidths=0.5,
fmt='.3f', figsize=(10, 10),
row_linkage=linkage_matrix,
col_linkage=linkage_matrix,
tree_kws={'linewidths': 1.5}
)
g.fig.suptitle('股票相关性聚类分析', fontsize=14, y=0.98)
plt.show()距离定义:基于相关性转换 \(d = 1 - |\rho|\)
三种链接方法:
| 方法 | 原理 | 特点 |
|---|---|---|
| 单链接 | 类间最小距离 | 易产生链式效应 |
| 完全链接 | 类间最大距离 | 类内直径小 |
| 平均链接 | 类间平均距离 | 最常用,本例采用 |
金融应用:发现同行业股票、识别风格因子、地域因子分析
将相关性表示为网络,直观展示股票关联结构:
plt.figure(figsize=(10, 8))
pos = nx.spring_layout(G, k=0.5, seed=42)
degrees = dict(G.degree())
# 节点:大小与连接数成正比
nx.draw_networkx_nodes(
G, pos,
node_size=[v * 300 for v in degrees.values()],
node_color='steelblue', alpha=0.7
)
# 边:绿色=正相关,红色=负相关
weights = [G[u][v]['weight'] for u, v in G.edges()]
edge_colors = ['red' if w < 0 else 'green' for w in weights]
nx.draw_networkx_edges(
G, pos,
width=[abs(w) * 3 for w in weights],
edge_color=edge_colors, alpha=0.6
)
nx.draw_networkx_labels(G, pos, font_size=10)
plt.title(f'股票相关性网络图(阈值={threshold})')
plt.axis('off')
plt.show()相关系数不是恒定的,会随市场状态变化:
stock_a = '中信证券'
stock_b = '中国平安'
window = 20 # 20日滚动窗口(约1个月)
# 计算滚动相关性
rolling_corr = log_returns[stock_a].rolling(
window
).corr(log_returns[stock_b])
fig, axes = plt.subplots(2, 1, figsize=(14, 10))
# 子图1:滚动相关系数
axes[0].plot(rolling_corr.index, rolling_corr, linewidth=2)
axes[0].axhline(y=0, color='k', linestyle='--')
axes[0].axhline(y=0.5, color='r', linestyle='--', label='阈值0.5')
axes[0].axhline(y=-0.5, color='r', linestyle='--', label='阈值-0.5')
axes[0].set_title(f'{stock_a}与{stock_b}的滚动相关性')
axes[0].legend()
# 子图2:归一化价格走势
norm_prices = df_prices[[stock_a, stock_b]] / \
df_prices[[stock_a, stock_b]].iloc[0] * 100
axes[1].plot(norm_prices[stock_a], label=stock_a)
axes[1].plot(norm_prices[stock_b], label=stock_b)
axes[1].set_title('归一化价格走势(初始=100)')
axes[1].legend()
plt.tight_layout()
plt.show()市场状态对相关性的影响:
correlation_matrix 与 rolling_pair:写出价格表字段、同一时间段的收益率样本量、3×3 方阵和滚动尾值;只有古典条件成立时才用 pearsonr 计算原始 p 值,并对三组资产对写出 FDR 调整后的 q 值。returns → correlation_matrix → rolling_pair,画出从数据输入、处理到结果;引用一个实际证券代码、相关系数或 p 值说明其作用,并写出交易日未对齐或窗口不足时的需要重新检查的情况。selected_codes 替换为另一组三只长三角公司,保持同一时间段的收益、矩阵对称性和60日滚动计算不变;写出替换字段、一条对角线为1的检查,并比较拓展应用前后方阵结构与滚动尾值。stock/stock_price_post_adjusted.parquet;长三角样本 600104.SH/600009.SH/600018.SH;字段 ts_code/trade_date/closefrom pathlib import Path # 导入路径工具
import pandas as pd # 导入表格库
from scipy.stats import pearsonr # 导入古典皮尔逊相关检验
from statsmodels.stats.multitest import multipletests # 导入多重检验FDR调整工具
path=Path('/home/ubuntu/r2_data_mount/data/stock/stock_price_post_adjusted.parquet') # 指定可用资产
if not path.exists(): raise FileNotFoundError('未找到课程数据文件,请从课程数据下载入口获取并核对文件位置') # 缺失即停止
selected_codes=['600104.SH','600009.SH','600018.SH'] # 事先设定上海代表性非金融公司
prices=pd.read_parquet(path,columns=['order_book_id','date','close']).rename(columns={'order_book_id':'ts_code','date':'trade_date'}) # 按真实Parquet 字段结构读取并标准化字段
prices['ts_code']=prices['ts_code'].str.replace('.XSHG','.SH',regex=False).str.replace('.XSHE','.SZ',regex=False) # 将本地证券代码统一为课堂后缀
prices=prices.loc[prices['ts_code'].isin(selected_codes)].copy() # 筛选事先设定对象
prices['trade_date']=pd.to_datetime(prices['trade_date']) # 统一交易日期
price_panel=prices.pivot(index='trade_date',columns='ts_code',values='close').sort_index() # 对齐共同交易日
returns=price_panel.pct_change(fill_method=None).dropna(how='any') # 计算同一时间段的收益率
correlation_matrix=returns.corr() # 计算静态相关方阵
rolling_pair=returns[selected_codes[0]].rolling(60).corr(returns[selected_codes[1]]) # 计算60日滚动相关
pair_correlation,p_value=pearsonr(returns[selected_codes[0]],returns[selected_codes[1]]) # 计算同一对资产的系数与p值
pairs=[(selected_codes[0],selected_codes[1]),(selected_codes[0],selected_codes[2]),(selected_codes[1],selected_codes[2])] # 列出三资产矩阵的全部检验族
pair_p_values=[pearsonr(returns[left_code],returns[right_code]).pvalue for left_code,right_code in pairs] # 在古典假设成立时计算各资产对原始p值
pair_q_values=multipletests(pair_p_values,method='fdr_bh')[1] # 对同一检验族执行Benjamini-Hochberg调整
assert correlation_matrix.shape==(3,3) and correlation_matrix.index.equals(correlation_matrix.columns) # 核对方阵维度与标签顺序
print(returns.shape,correlation_matrix.round(3),rolling_pair.dropna().tail(),{'pair_correlation':pair_correlation,'raw_p_value':p_value,'FDR_q_values':pair_q_values}) # 输出相关、滚动与多重检验依据pearsonr 交叉核对。3×3 相关矩阵、60 日滚动相关的最新值、指定资产对系数、原始 p 值与三对资产的 FDR q 值。若常规检验条件不成立,可改用区组 bootstrap 置信区间。股票行情可从课程数据下载入口取得。[商业大数据分析与应用]